package com.ochnas.volunteersignup.domain;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Transient;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentFactory;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;

@Entity
public class Position implements Serializable {
	@Transient
	private static Comparator<TimeSlot> sortByAscendingTime = new Comparator<TimeSlot>() {

		@Override
		public int compare(TimeSlot t1, TimeSlot t2) {
			Time t1StartTime = t1.getStartTime();
			Time t2StartTimie = t2.getStartTime();
			if (t1StartTime.getHourOfDay() > t2StartTimie.getHourOfDay()) {
				return 1;
			} else if (t1StartTime.getHourOfDay() < t2StartTimie.getHourOfDay()) {
				return -1;
			} else {
				if (t1StartTime.getMinutes() > t2StartTimie.getMinutes()) {
					return 1;
				} else if (t1StartTime.getMinutes() < t2StartTimie.getMinutes()) {
					return -1;
				}
			}
			return 1;
		}
		
	};

	@Id @GeneratedValue
	private Long id;
	private String name;
	private String description;
	private int requiredNumberOfVolunteers;
	
	@ManyToOne
	private Event event;
	
	@OneToMany(cascade = CascadeType.ALL, mappedBy = "position")
	private List<TimeSlot> timeSlots = new ArrayList<TimeSlot>();
	
	/**
	 * No-argument constructor.
	 */
	public Position() {
		super();
	}

	/**
	 * Position-defined constructor.
	 * @param name
	 * @param description
	 * @param requiredNumberOfVolunteers
	 */
	public Position(String name, String description,
			int requiredNumberOfVolunteers) {
		super();
		this.name = name;
		this.description = description;
		this.requiredNumberOfVolunteers = requiredNumberOfVolunteers;
	}
	
	/**
	 * Method to add TimeSlot 
	 * @param timeSlot - TimeSlot
	 * @return
	 */
	public TimeSlot addTimeSlot(TimeSlot timeSlot) {
		timeSlot.setPosition(this);
		timeSlots.add(timeSlot);
		return timeSlot;
	}
	
	
	/**
	 * Method to remove a time slot from the list of TimeSlots.
	 * @param timeSlots - TimeSlot
	 * @return boolean - true, if removal is successful; false, otherwise.
	 */
	public boolean removeTimeSlot(TimeSlot timeSlot) {
		return timeSlots.remove(timeSlot);
	}
	
	/**
	 * Method to sort the time slots in the list of TimeSlot
	 * @return
	 */
	public List<TimeSlot> getTimeSlots() {
		TimeSlot[] tsArray = timeSlots.toArray(new TimeSlot[0]);
		Arrays.sort(tsArray, sortByAscendingTime);
		return Collections.unmodifiableList(Arrays.asList(tsArray));
	}
	
	
	/**
	 * Method to return Position id
	 * @return Long
	 */
	public Long getId() {
		return id;
	}
	
	/**
	 * Method to set Position id
	 * @param id Long
	 */
	public void setId(Long id) {
		this.id = id;
	}
	
	/**
	 * Method to return Position name
	 * @return String
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * Method to set Position name
	 * @param name
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * Method to return the description of the position
	 * @return String
	 */
	public String getDescription() {
		return description;
	}
	
	/**
	 * Method to set the description of the postion
	 * @param description
	 */
	public void setDescription(String description) {
		this.description = description;
	}
	
	/**
	 * Method to return the required number of volunteers
	 *   for the position
	 * @return int
	 */
	public int getRequiredNumberOfVolunteers() {
		return requiredNumberOfVolunteers;
	}
	
	/**
	 * Method to set the required number of volunteers
	 *   for the position
	 * @param requiredNumberOfVolunteers
	 */
	public void setRequiredNumberOfVolunteers(int requiredNumberOfVolunteers) {
		this.requiredNumberOfVolunteers = requiredNumberOfVolunteers;
	}

	public Event getEvent() {
		return event;
	}

	public void setEvent(Event event) {
		this.event = event;
	}

	@Override
	public String toString() {
		StringBuffer strBuff = new StringBuffer("Position[id=");
		strBuff.append(id);
		strBuff.append("]{name=");
		strBuff.append(name);
		strBuff.append(",description=");
		strBuff.append(description);
		strBuff.append(",requiredNumberOfVolunteers=");
		strBuff.append(requiredNumberOfVolunteers);
		strBuff.append(",eventId=");
		if (event == null) {
			strBuff.append(event);
		} else {
			strBuff.append(event.getId());
		}
		strBuff.append("}");
		return strBuff.toString();
	}

	public void setTimeSlots(List<TimeSlot> timeSlots) {
		this.timeSlots = timeSlots;
	}
	
    public String toXml() {  	
        Document response = DocumentFactory.getInstance().createDocument();
        Element positionElement = response.addElement("position");
        positionElement.addAttribute("id", String.valueOf(getId()));
        positionElement.addElement("name")
        	.addText(getName());
        positionElement.addElement("description")
        	.addText((getDescription()==null?"":getDescription()));
        positionElement.addElement("requiredNumberOfVolunteers")
        	.addText(String.valueOf(getRequiredNumberOfVolunteers()));
        
        Element eventElement = positionElement.addElement("event")
        	.addAttribute("id", String.valueOf(getEvent().getId()));
        
        for (TimeSlot t: timeSlots) {
        	Element timeSlotElement = positionElement.addElement("timeslot")
        		.addAttribute("id", String.valueOf(t.getId()));
        	timeSlotElement.addElement("quantityOfVolunteers")
        		.addText(String.valueOf(t.getQuantityOfVolunteers()));
    		Element tsPosition = timeSlotElement.addElement("position")
    			.addAttribute("id", String.valueOf(t.getPosition().getId()));
    		Element startTime = timeSlotElement.addElement("time")
    			.addAttribute("name", "startTime");
    		startTime.addElement("hourOfDay")
    			.addText(String.valueOf(t.getStartTime().getHourOfDay()));
    		startTime.addElement("minutes")
    			.addText(String.valueOf(t.getStartTime().getMinutes()));
    		
    		Element endTime = timeSlotElement.addElement("time")
    			.addAttribute("name", "endTime");
    		endTime.addElement("hourOfDay")
    			.addText(String.valueOf(t.getEndTime().getHourOfDay()));
    		endTime.addElement("minutes")
    			.addText(String.valueOf(t.getEndTime().getMinutes()));
        }
        return response.asXML();
    }
	
    
    public static Position  fromXml(String xml) {
		try {
			Document doc = DocumentHelper.parseText(xml);
			
			Node eNode = doc.selectSingleNode("/position/event");
			long eId = Long.parseLong(eNode.valueOf("@id"));
			Event e = new Event();
			e.setId(eId);
			
	        Node pNode = doc.selectSingleNode("/position");
			long pId = Long.parseLong(pNode.valueOf("@id"));
			Node pName = doc.selectSingleNode("/position/name");
			String pNameTxt = pName.getText();
			Node pDescription = doc.selectSingleNode("/position/description");
			String pDescriptionTxt = pDescription.getText();
			Node rnvNode = doc.selectSingleNode("/position/requiredNumberOfVolunteers");
			int prequiredNumberOfVolunteers = Integer.parseInt(
					rnvNode.getText());

			Position p = new Position(pNameTxt, pDescriptionTxt,
					prequiredNumberOfVolunteers);
			p.setId(pId);
			p.setEvent(e); // set event - only contains event id
			
			// Parse for position's time slots
			List tsList = pNode.selectNodes("/position/timeslot");
			for (Iterator iter2 = tsList.iterator(); iter2.hasNext();) {
				Node tsNode = (Node) iter2.next();
				// get time slot's id
				long tsId = Long.parseLong(tsNode.valueOf("@id"));
				// get time slot's start time
				Node tsStNode = tsNode
						.selectSingleNode("time[@name='startTime']");
				Node tsStHourOfDayNode = tsStNode.selectSingleNode("hourOfDay");
				int tsStHourOfDay = Integer.parseInt(tsStHourOfDayNode.getText());
				Node tsStMinutesNode = tsStNode.selectSingleNode("minutes");
				int tsStMinutes = Integer.parseInt(tsStMinutesNode.getText());
				Time tsStTime = new Time(tsStHourOfDay, tsStMinutes);
				// get time slot's end time
				Node tsEtNode = tsNode
						.selectSingleNode("time[@name='endTime']");
				
				Node tsEtHourOfDayNode = tsEtNode.selectSingleNode("hourOfDay");
				int tsEtHourOfDay = Integer.parseInt(tsEtHourOfDayNode.getText());
				Node tsEtMinutesNode = tsEtNode.selectSingleNode("minutes");
				int tsEtMinutes = Integer.parseInt(tsEtMinutesNode.getText());
				Time tsEtTime = new Time(tsEtHourOfDay, tsEtMinutes);
				
				// get time slot's qty of volunteers
				Node qvNode = tsNode.selectSingleNode("quantityOfVolunteers");
				int qtyOfVolunteers = Integer.parseInt(qvNode.getText());

				TimeSlot ts = new TimeSlot(tsStTime, tsEtTime, qtyOfVolunteers);
				ts.setId(tsId);

				// add time slot to position
				p.addTimeSlot(ts);
			}
			
			return p;
		
		} catch (DocumentException e) {
			throw new RuntimeException("Invalid event xml: " + xml, e);
		} catch (NumberFormatException e) {
			throw new RuntimeException("Invalid event xml: " + xml, e);
		} catch (Exception e) {
			throw new RuntimeException("Invalid event xml: " + xml, e);
		}
		
	}
}
